/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {
  ts,
  AstTreeUtils,
  ArkFile,
  Local,
  ArkAssignStmt,
  AbstractFieldRef,
  ClassSignature,
} from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from "arkanalyzer/lib/utils/logger";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import {
  FileMatcher,
  MatcherCallback,
  MatcherTypes,
} from "../../matcher/Matchers";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { Utils } from "../../utils/common/Utils";
import { RuleFix } from "../../model/Fix";
//methodParams
type Options = {
  allowPrivateClassPropertyAccess: boolean;
  allowProtectedClassPropertyAccess: boolean;
  allowIndexSignaturePropertyAccess: boolean;
  allowKeywords: boolean;
  allowPattern: string;
};
type posionS = {
  startLine: number;
  startCol: number;
  endLine: number;
  endCol: number;
};

const defaultOptions1: Options = {
  allowPrivateClassPropertyAccess: false,
  allowProtectedClassPropertyAccess: false, //默认fail
  allowIndexSignaturePropertyAccess: false, //默认fail
  allowKeywords: true,
  allowPattern: "",
};

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, "DotNotationCheck");
const gMetaData: BaseMetaData = {
  severity: 1,
  ruleDocPath: "docs/dot-notation-check.md",
  description: "is better written in dot notation.",
};
//要求或不允许函数标识符和它们的调用之间有空格
export class DotNotationCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private issueMap: Map<string, IssueReport> = new Map();
  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const fileMatchBuildCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check,
    };
    return [fileMatchBuildCb];
  }

  public check = (targetFile: ArkFile) => {
    const filePath = targetFile.getName();
    const isTs = this.getFileExtension(filePath, "ts");
    if (!isTs) {
      return;
    }
    let options = (this.rule.option[0] as Options) || defaultOptions1;
    let mergedOptions: Options = {
      ...defaultOptions1,
      ...options,
    };
    this.issueMap.clear();
    let arfilecode = targetFile.getCode();
    const arkfilePath = targetFile.getFilePath();
    // 找到当前分割符所在行
    let lineBreak = this.getLineBreak(arfilecode);
    const scene = targetFile.getScene();
    const classes = targetFile.getClasses();
    const allowKeywords = mergedOptions.allowKeywords;
    const allowPrivateClassPropertyAccess =
      mergedOptions.allowPrivateClassPropertyAccess;
    const allowProtectedClassPropertyAccess =
      mergedOptions.allowProtectedClassPropertyAccess;
    const allowIndexSignaturePropertyAccess =
      mergedOptions.allowIndexSignaturePropertyAccess;
    const regex = new RegExp(mergedOptions.allowPattern);
    for (const clas of classes) {
      const methods = clas.getMethods();
      for (const med of methods) {
        const varLocal: Map<string, Local> =
          med.getBody()?.getLocals() ?? new Map<string, Local>();
        for (const [key, local] of varLocal) {
          const declareStmt = local.getDeclaringStmt();
          const position = declareStmt?.getOriginPositionInfo();

          if (declareStmt instanceof ArkAssignStmt) {
            const rightOp = declareStmt.getRightOp();
            if (rightOp instanceof AbstractFieldRef) {
              const fieldsign = rightOp.getFieldSignature();
              const classSign = fieldsign.getDeclaringSignature();
              const declareOrigtext = declareStmt.getOriginalText() ?? "";
              if (classSign instanceof ClassSignature) {
                const clas = scene.getClass(classSign);
                const arkField = clas?.getField(fieldsign);
                //有定义class的
                if (arkField) {
                  const fieldName = arkField?.getName();
                  const isPrivate = arkField?.isPrivate();
                  const isProtected = arkField?.isProtected();
                  const posionFields = this.fieldUsed(
                    declareOrigtext,
                    fieldName
                  );
                  const lineStartNo = position?.getLineNo() ?? -1;
                  const lineStartCol = position?.getColNo() ?? -1;
                  let cnt = 0;
                  for (let index = 1; index < lineStartNo; index++) {
                    cnt = arfilecode.indexOf(lineBreak, cnt + 1);
                  }
                  //对第一行第一列特殊处理，后续代码都是以0为起始偏移，所以需要+1
                  const start =
                    cnt === 0 && lineStartNo === 1 ? 0 : cnt + lineStartCol + 1;
                  if (allowKeywords) {
                    if (allowPrivateClassPropertyAccess && isPrivate) {
                      continue;
                    } else if (
                      allowProtectedClassPropertyAccess &&
                      isProtected
                    ) {
                      continue;
                    } else if (
                      !(
                        mergedOptions.allowPattern != "" &&
                        regex.test(fieldName)
                      )
                    ) {
                      this.addFix(
                        arkfilePath,
                        declareOrigtext,
                        fieldName,
                        start,
                        lineStartNo,
                        lineStartCol,
                        posionFields
                      );
                    }
                  } else {
                    this.addFixByDot(
                      arkfilePath,
                      declareOrigtext,
                      fieldName,
                      start,
                      lineStartNo,
                      lineStartCol,
                      posionFields
                    );
                  }
                } else {
                  //没有class类的
                  const fieldName = this.extractPropertyName(declareOrigtext);
                  const posionFieldst = this.fieldUsed(
                    declareOrigtext,
                    fieldName
                  );
                  const lineStartNot = position?.getLineNo() ?? -1;
                  const lineStartColt = position?.getColNo() ?? -1;
                  let cntt = 0;
                  for (let index = 1; index < lineStartNot; index++) {
                    cntt = arfilecode.indexOf(lineBreak, cntt + 1);
                  }
                  //对第一行第一列特殊处理，后续代码都是以0为起始偏移，所以需要+1
                  const start =
                    cntt === 0 && lineStartNot === 1
                      ? 0
                      : cntt + lineStartColt + 1;
                  if (allowKeywords) {
                    if (allowIndexSignaturePropertyAccess && clas) {
                      continue;
                    } else if (
                      !(
                        mergedOptions.allowPattern != "" &&
                        regex.test(fieldName)
                      )
                    ) {
                      this.addFix(
                        arkfilePath,
                        declareOrigtext,
                        fieldName,
                        start,
                        lineStartNot,
                        lineStartColt,
                        posionFieldst
                      );
                    }
                  } else {
                    this.addFixByDot(
                      arkfilePath,
                      declareOrigtext,
                      fieldName,
                      start,
                      lineStartNot,
                      lineStartColt,
                      posionFieldst
                    );
                  }
                }
              }
            }
          }
        }
      }
    }
    this.reportSortedIssues();
  };
  checkIndexSignature(node: ts.Node): boolean {
    // 如果当前节点是索引签名声明，直接返回 true
    if (ts.isIndexSignatureDeclaration(node)) {
      return true;
    }

    // 获取当前节点的子节点
    const children = node.getChildren();

    // 遍历子节点并递归检查
    for (const child of children) {
      // 如果在子节点中找到索引签名声明，立刻返回 true
      if (this.checkIndexSignature(child)) {
        return true;
      }
    }

    // 如果没有找到索引签名声明，返回 false
    return false;
  }

  getdeclareNode(
    childr: ts.Node | undefined,
    program: ts.Program
  ): ts.Node | undefined {
    if (!childr) {
      return undefined;
    }
    const propertySymbol = program.getTypeChecker().getSymbolAtLocation(childr);
    const cvb = propertySymbol?.declarations?.[0] ?? undefined;
    // 如果是类声明
    if (!cvb) {
      return undefined;
    }
    return cvb;
  }
  // 判断一个字符串是否为关键字或操作符
  private isKeyword(str: string) {
    const keywords = [
      // JavaScript 关键字
      "if",
      "else",
      "for",
      "while",
      "switch",
      "case",
      "break",
      "continue",
      "return",
      "function",
      "class",
      "const",
      "let",
      "var",
      "try",
      "catch",
      "finally",
      "throw",
      "new",
      "this",
      "super",
      "import",
      "export",
      "default",
      "void",
      "undefined",
      "null",
      "true",
      "false",
      "await",
      "async",
      "instanceof",
      "typeof",
      "in",
      "debugger",
      "yield",
      "eval",
      "arguments",
      "delete",
      "any",
      "unknown",
      "never",
      "type",
      "interface",
      "enum",
      "as",
      "extends",
      "implements",
      "infer",
      "declare",
      "module",
      "namespace",
      "public",
      "private",
      "protected",
      "readonly",
    ];
    return keywords.includes(str);
  }
  private addIssueReport(
    arkFilePath: string,
    line: number,
    startCol: number,
    endCol: number,
    name: string,
    message: string
  ): Defects {
    const severity = this.rule.alert ?? this.metaData.severity;
    const defect = new Defects(
      line,
      startCol,
      endCol,
      message,
      severity,
      this.rule.ruleId,
      arkFilePath,
      this.metaData.ruleDocPath,
      true,
      false,
      false
    );
    this.defects.push(defect);
    return defect;
  }

  private ruleFix(pos: number, end: number, text: string): RuleFix {
    return { range: [pos, end], text: text };
  }

  private reportSortedIssues(): void {
    if (this.issueMap.size === 0) {
      return;
    }

    const sortedIssues = Array.from(this.issueMap.entries()).sort(
      ([keyA], [keyB]) => Utils.sortByLineAndColumn(keyA, keyB)
    );

    this.issues = [];

    sortedIssues.forEach(([_, issue]) => {
      RuleListUtil.push(issue.defect);
      this.issues.push(issue);
    });
  }
  private getFileExtension(filePath: string, filetype: string): boolean {
    // 使用正则表达式匹配文件后缀
    const match = filePath.match(/\.([0-9a-zA-Z]+)$/);

    // 如果匹配到了扩展名，且扩展名等于 filetype，则返回扩展名，否则返回空字符串
    if (match) {
      const extension = match[1];
      return extension === filetype;
    }

    return false;
  }

  //判断是否是[]调用
  private fieldUsed(origText: string, fieldname: string): posionS {
    // 转义字段，避免正则特殊字符冲突
    const escapedField = fieldname.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

    // 正则匹配 `obj['property']` 或 `obj["property"]`
    const bracketRegex = new RegExp(
      `(\\w+)\\s*\\[\\s*(['"])${escapedField}\\2\\s*\\]`,
      "g"
    );
    // 正则匹配 `obj.property`
    const dotRegex = new RegExp(`(\\w+)\\s*\\.\\s*${escapedField}\\b`, "g");

    let result: posionS = { startLine: 0, startCol: 0, endLine: 0, endCol: 0 };

    let match;

    // 处理 `obj['property']`
    while ((match = bracketRegex.exec(origText)) !== null) {
      const bracketStartIdx = match.index + match[0].indexOf("["); // `[` 的索引
      const bracketEndIdx = match.index + match[0].lastIndexOf("]"); // `]` 的索引

      // 计算 `[` 的行列号
      const beforeStart = origText.substring(0, bracketStartIdx);
      const startLines = beforeStart.split("\n");
      result.startLine = startLines.length;
      result.startCol = startLines[startLines.length - 1].length + 1;

      // 计算 `]` 的行列号
      const beforeEnd = origText.substring(0, bracketEndIdx + 1);
      const endLines = beforeEnd.split("\n");
      result.endLine = endLines.length;
      result.endCol = endLines[endLines.length - 1].length + 1;
    }

    // 处理 `obj.property`
    while ((match = dotRegex.exec(origText)) !== null) {
      const dotIdx = match.index + match[0].indexOf("."); // `.` 的索引
      const propEndIdx = dotIdx + 1 + fieldname.length; // `property` 结束的索引

      // 计算 `.` 的行列号
      const beforeDot = origText.substring(0, dotIdx);
      const dotLines = beforeDot.split("\n");
      result.startLine = dotLines.length;
      result.startCol = dotLines[dotLines.length - 1].length + 1;

      // 计算 `property` 结束的行列号
      const beforePropEnd = origText.substring(0, propEndIdx);
      const propEndLines = beforePropEnd.split("\n");
      result.endLine = propEndLines.length;
      result.endCol = propEndLines[propEndLines.length - 1].length + 1;
    }

    // 如果没有匹配到任何方式，返回 0
    return result;
  }

  private addFixByDot(
    filePath: string,
    declareOrigtext: string,
    fieldName: string,
    start: number,
    lineStartNo: number,
    lineStartCol: number,
    posionFields: posionS
  ) {
    // const start = FixUtils.getRangeStart(targetFile, declareStmt);

    let start1 =
      lineStartNo === 0 && lineStartCol === 1
        ? 0
        : lineStartNo + lineStartNo + 1;
    const astRoot = AstTreeUtils.getASTNode(fieldName, declareOrigtext);
    const matchingNodes: ts.Node[] = [];

    astRoot.forEachChild((child) => {
      const collect = this.collectNodes(child);
      matchingNodes.push(...collect);
    });
    matchingNodes.forEach((chd) => {
      // 处理 `obj.key` 形式
      if (ts.isPropertyAccessExpression(chd) && this.isKeyword(fieldName)) {
        chd.getChildren().forEach((node) => {
          if (node.getText() !== fieldName || !this.isKeyword(fieldName))
            return;

          const defect = this.addIssueReport(
            filePath,
            lineStartNo,
            lineStartCol + posionFields.startCol,
            lineStartCol + posionFields.startCol + fieldName.length,
            `.${fieldName}`,
            `.${fieldName} ` + this.metaData.description
          );

          const fix: RuleFix = this.ruleFix(
            start + node.getStart(),
            start + node.getEnd(),
            `["${fieldName}"]`
          );

          this.issueMap.set(defect.fixKey, { defect, fix });
        });
      }
    });
  }
  private addFix(
    filePath: string,
    declareOrigtext: string,
    fieldName: string,
    start: number,
    lineStartNo: number,
    lineStartCol: number,
    posionFields: posionS
  ) {
    const astRoot = AstTreeUtils.getASTNode(fieldName, declareOrigtext);
    const matchingNodes: ts.Node[] = [];

    astRoot.forEachChild((child) => {
      const collect = this.collectNodes(child);
      matchingNodes.push(...collect);
    });
    matchingNodes.forEach((chd) => {
      // 处理 `obj["key"]` 形式
      if (ts.isElementAccessExpression(chd)) {
        const [
          expression,
          openBracketToken,
          argumentExpression,
          closeBracketToken,
        ] = chd.getChildren();

        if (!ts.isStringLiteral(argumentExpression)) return;

        const defect = this.addIssueReport(
          filePath,
          lineStartNo,
          lineStartCol + (posionFields?.startCol ?? 0),
          lineStartCol + (posionFields?.startCol ?? 0) + fieldName.length,
          `[${fieldName}]`,
          `[${fieldName}] ` + this.metaData.description
        );

        const fix: RuleFix = this.ruleFix(
          start + openBracketToken.getStart(),
          start + closeBracketToken.getEnd(),
          `.${fieldName}`
        );

        this.issueMap.set(defect.fixKey, { defect, fix });
      }
    });
  }
  private extractPropertyName(origText: string): string {
    // 1. 先匹配 vb.propertyName
    const dotMatch = origText.match(/\b\w+\s*\.\s*([\w$]+)\b/);
    if (dotMatch) {
      return dotMatch[1]; // 返回 propertyName
    }

    // 2. 再匹配 vb['propertyName'] 或 vb["propertyName"]
    const bracketMatch = origText.match(/\b\w+\s*\[\s*(['"])(.*?)\1\s*\]/);
    if (bracketMatch) {
      return bracketMatch[2]; // 返回 propertyName
    }

    return "errorProperty"; // 没有匹配到
  }

  //检查所有的节点
  private collectNodes(node: ts.Node): ts.Node[] {
    const matchingNodes: ts.Node[] = [];

    // 如果节点是 ElementAccessExpression，添加到数组
    if (ts.isElementAccessExpression(node)) {
      matchingNodes.push(node);
    }

    // 如果节点是 PropertyAccessExpression，添加到数组
    if (ts.isPropertyAccessExpression(node)) {
      matchingNodes.push(node);
    }

    // 递归遍历所有子节点
    ts.forEachChild(node, (childNode) => {
      // 将子节点符合条件的节点合并到当前匹配节点数组
      matchingNodes.push(...this.collectNodes(childNode));
    });

    return matchingNodes;
  }
  //获取分割符
  private getLineBreak(text: string): string {
    if (text.includes("\r\n")) {
      return "\r\n";
    } else if (text.includes("\n")) {
      return "\n";
    } else {
      return "\r";
    }
  }
}
